/*
 * EAP-TNC - TNCS (IF-IMV, IF-TNCCS, and IF-TNCCS-SOH)
 * Copyright (c) 2007-2008, Jouni Malinen <j@w1.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#include "includes.h"
#include <dlfcn.h>

#include "common.h"
#include "base64.h"
#include "tncs.h"
#include "eap_common/eap_tlv_common.h"
#include "eap_common/eap_defs.h"


/* TODO: TNCS must be thread-safe; review the code and add locking etc. if
 * needed.. */

#define TNC_CONFIG_FILE "/etc/tnc_config"
#define IF_TNCCS_START \
"<?xml version=\"1.0\"?>\n" \
"<TNCCS-Batch BatchId=\"%d\" Recipient=\"TNCS\" " \
"xmlns=\"http://www.trustedcomputinggroup.org/IWG  NC/1_0/IF_TNCCS#\" " \
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " \
"xsi:schemaLocation=\"http://www.trustedcomputinggroup.org/IWG  NC/1_0/" \
"IF_TNCCS# https://www.trustedcomputinggroup.org/XML/SCHEMA/TNCCS_1.0.xsd\">\n"
#define IF_TNCCS_END "\n</TNCCS-Batch>"

/* TNC IF-IMV */

typedef unsigned long TNC_UInt32;
typedef unsigned char *TNC_BufferReference;

typedef TNC_UInt32 TNC_IMVID;
typedef TNC_UInt32 TNC_ConnectionID;
typedef TNC_UInt32 TNC_ConnectionState;
typedef TNC_UInt32 TNC_RetryReason;
typedef TNC_UInt32 TNC_IMV_Action_Recommendation;
typedef TNC_UInt32 TNC_IMV_Evaluation_Result;
typedef TNC_UInt32 TNC_MessageType;
typedef TNC_MessageType *TNC_MessageTypeList;
typedef TNC_UInt32 TNC_VendorID;
typedef TNC_UInt32 TNC_Subtype;
typedef TNC_UInt32 TNC_Version;
typedef TNC_UInt32 TNC_Result;
typedef TNC_UInt32 TNC_AttributeID;

typedef TNC_Result (*TNC_TNCS_BindFunctionPointer)(
        TNC_IMVID imvID,
        char *functionName,
        void **pOutfunctionPointer);

#define TNC_RESULT_SUCCESS 0
#define TNC_RESULT_NOT_INITIALIZED 1
#define TNC_RESULT_ALREADY_INITIALIZED 2
#define TNC_RESULT_NO_COMMON_VERSION 3
#define TNC_RESULT_CANT_RETRY 4
#define TNC_RESULT_WONT_RETRY 5
#define TNC_RESULT_INVALID_PARAMETER 6
#define TNC_RESULT_CANT_RESPOND 7
#define TNC_RESULT_ILLEGAL_OPERATION 8
#define TNC_RESULT_OTHER 9
#define TNC_RESULT_FATAL 10

#define TNC_CONNECTION_STATE_CREATE 0
#define TNC_CONNECTION_STATE_HANDSHAKE 1
#define TNC_CONNECTION_STATE_ACCESS_ALLOWED 2
#define TNC_CONNECTION_STATE_ACCESS_ISOLATED 3
#define TNC_CONNECTION_STATE_ACCESS_NONE 4
#define TNC_CONNECTION_STATE_DELETE 5

#define TNC_IFIMV_VERSION_1 1

#define TNC_VENDORID_ANY ((TNC_VendorID) 0xffffff)
#define TNC_SUBTYPE_ANY ((TNC_Subtype) 0xff)

/* TNCC-TNCS Message Types */
#define TNC_TNCCS_RECOMMENDATION                0x00000001
#define TNC_TNCCS_ERROR                                0x00000002
#define TNC_TNCCS_PREFERREDLANGUAGE                0x00000003
#define TNC_TNCCS_REASONSTRINGS                        0x00000004

/* Possible TNC_IMV_Action_Recommendation values: */
enum IMV_Action_Recommendation {
        TNC_IMV_ACTION_RECOMMENDATION_ALLOW,
        TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS,
        TNC_IMV_ACTION_RECOMMENDATION_ISOLATE,
        TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION
};

/* Possible TNC_IMV_Evaluation_Result values: */
enum IMV_Evaluation_Result {
        TNC_IMV_EVALUATION_RESULT_COMPLIANT,
        TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MINOR,
        TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MAJOR,
        TNC_IMV_EVALUATION_RESULT_ERROR,
        TNC_IMV_EVALUATION_RESULT_DONT_KNOW
};

struct tnc_if_imv {
        struct tnc_if_imv *next;
        char *name;
        char *path;
        void *dlhandle; /* from dlopen() */
        TNC_IMVID imvID;
        TNC_MessageTypeList supported_types;
        size_t num_supported_types;

        /* Functions implemented by IMVs (with TNC_IMV_ prefix) */
        TNC_Result (*Initialize)(
                TNC_IMVID imvID,
                TNC_Version minVersion,
                TNC_Version maxVersion,
                TNC_Version *pOutActualVersion);
        TNC_Result (*NotifyConnectionChange)(
                TNC_IMVID imvID,
                TNC_ConnectionID connectionID,
                TNC_ConnectionState newState);
        TNC_Result (*ReceiveMessage)(
                TNC_IMVID imvID,
                TNC_ConnectionID connectionID,
                TNC_BufferReference message,
                TNC_UInt32 messageLength,
                TNC_MessageType messageType);
        TNC_Result (*SolicitRecommendation)(
                TNC_IMVID imvID,
                TNC_ConnectionID connectionID);
        TNC_Result (*BatchEnding)(
                TNC_IMVID imvID,
                TNC_ConnectionID connectionID);
        TNC_Result (*Terminate)(TNC_IMVID imvID);
        TNC_Result (*ProvideBindFunction)(
                TNC_IMVID imvID,
                TNC_TNCS_BindFunctionPointer bindFunction);
};


#define TNC_MAX_IMV_ID 10

struct tncs_data {
        struct tncs_data *next;
        struct tnc_if_imv *imv; /* local copy of tncs_global_data->imv */
        TNC_ConnectionID connectionID;
        unsigned int last_batchid;
        enum IMV_Action_Recommendation recommendation;
        int done;

        struct conn_imv {
                u8 *imv_send;
                size_t imv_send_len;
                enum IMV_Action_Recommendation recommendation;
                int recommendation_set;
        } imv_data[TNC_MAX_IMV_ID];

        char *tncs_message;
};


struct tncs_global {
        struct tnc_if_imv *imv;
        TNC_ConnectionID next_conn_id;
        struct tncs_data *connections;
};

static struct tncs_global *tncs_global_data = NULL;


static struct tnc_if_imv * tncs_get_imv(TNC_IMVID imvID)
{
        struct tnc_if_imv *imv;

        if (imvID >= TNC_MAX_IMV_ID || tncs_global_data == NULL)
                return NULL;
        imv = tncs_global_data->imv;
        while (imv) {
                if (imv->imvID == imvID)
                        return imv;
                imv = imv->next;
        }
        return NULL;
}


static struct tncs_data * tncs_get_conn(TNC_ConnectionID connectionID)
{
        struct tncs_data *tncs;

        if (tncs_global_data == NULL)
                return NULL;

        tncs = tncs_global_data->connections;
        while (tncs) {
                if (tncs->connectionID == connectionID)
                        return tncs;
                tncs = tncs->next;
        }

        wpa_printf(MSG_DEBUG, "TNC: Connection ID %lu not found",
                   (unsigned long) connectionID);

        return NULL;
}


/* TNCS functions that IMVs can call */
TNC_Result TNC_TNCS_ReportMessageTypes(
        TNC_IMVID imvID,
        TNC_MessageTypeList supportedTypes,
        TNC_UInt32 typeCount)
{
        TNC_UInt32 i;
        struct tnc_if_imv *imv;

        wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_ReportMessageTypes(imvID=%lu "
                   "typeCount=%lu)",
                   (unsigned long) imvID, (unsigned long) typeCount);

        for (i = 0; i < typeCount; i++) {
                wpa_printf(MSG_DEBUG, "TNC: supportedTypes[%lu] = %lu",
                           i, supportedTypes[i]);
        }

        imv = tncs_get_imv(imvID);
        if (imv == NULL)
                return TNC_RESULT_INVALID_PARAMETER;
        os_free(imv->supported_types);
        imv->supported_types =
                os_malloc(typeCount * sizeof(TNC_MessageTypeList));
        if (imv->supported_types == NULL)
                return TNC_RESULT_FATAL;
        os_memcpy(imv->supported_types, supportedTypes,
                  typeCount * sizeof(TNC_MessageTypeList));
        imv->num_supported_types = typeCount;

        return TNC_RESULT_SUCCESS;
}


TNC_Result TNC_TNCS_SendMessage(
        TNC_IMVID imvID,
        TNC_ConnectionID connectionID,
        TNC_BufferReference message,
        TNC_UInt32 messageLength,
        TNC_MessageType messageType)
{
        struct tncs_data *tncs;
        unsigned char *b64;
        size_t b64len;

        wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_SendMessage(imvID=%lu "
                   "connectionID=%lu messageType=%lu)",
                   imvID, connectionID, messageType);
        wpa_hexdump_ascii(MSG_DEBUG, "TNC: TNC_TNCS_SendMessage",
                          message, messageLength);

        if (tncs_get_imv(imvID) == NULL)
                return TNC_RESULT_INVALID_PARAMETER;

        tncs = tncs_get_conn(connectionID);
        if (tncs == NULL)
                return TNC_RESULT_INVALID_PARAMETER;

        b64 = base64_encode(message, messageLength, &b64len);
        if (b64 == NULL)
                return TNC_RESULT_FATAL;

        os_free(tncs->imv_data[imvID].imv_send);
        tncs->imv_data[imvID].imv_send_len = 0;
        tncs->imv_data[imvID].imv_send = os_zalloc(b64len + 100);
        if (tncs->imv_data[imvID].imv_send == NULL) {
                os_free(b64);
                return TNC_RESULT_OTHER;
        }

        tncs->imv_data[imvID].imv_send_len =
                os_snprintf((char *) tncs->imv_data[imvID].imv_send,
                            b64len + 100,
                            "<IMC-IMV-Message><Type>%08X</Type>"
                            "<Base64>%s</Base64></IMC-IMV-Message>",
                            (unsigned int) messageType, b64);

        os_free(b64);

        return TNC_RESULT_SUCCESS;
}


TNC_Result TNC_TNCS_RequestHandshakeRetry(
        TNC_IMVID imvID,
        TNC_ConnectionID connectionID,
        TNC_RetryReason reason)
{
        wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_RequestHandshakeRetry");
        /* TODO */
        return TNC_RESULT_SUCCESS;
}


TNC_Result TNC_TNCS_ProvideRecommendation(
        TNC_IMVID imvID,
        TNC_ConnectionID connectionID,
        TNC_IMV_Action_Recommendation recommendation,
        TNC_IMV_Evaluation_Result evaluation)
{
        struct tncs_data *tncs;

        wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_ProvideRecommendation(imvID=%lu "
                   "connectionID=%lu recommendation=%lu evaluation=%lu)",
                   (unsigned long) imvID, (unsigned long) connectionID,
                   (unsigned long) recommendation, (unsigned long) evaluation);

        if (tncs_get_imv(imvID) == NULL)
                return TNC_RESULT_INVALID_PARAMETER;

        tncs = tncs_get_conn(connectionID);
        if (tncs == NULL)
                return TNC_RESULT_INVALID_PARAMETER;

        tncs->imv_data[imvID].recommendation = recommendation;
        tncs->imv_data[imvID].recommendation_set = 1;

        return TNC_RESULT_SUCCESS;
}


TNC_Result TNC_TNCS_GetAttribute(
        TNC_IMVID imvID,
        TNC_ConnectionID connectionID,
        TNC_AttributeID attribureID,
        TNC_UInt32 bufferLength,
        TNC_BufferReference buffer,
        TNC_UInt32 *pOutValueLength)
{
        wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_GetAttribute");
        /* TODO */
        return TNC_RESULT_SUCCESS;
}


TNC_Result TNC_TNCS_SetAttribute(
        TNC_IMVID imvID,
        TNC_ConnectionID connectionID,
        TNC_AttributeID attribureID,
        TNC_UInt32 bufferLength,
        TNC_BufferReference buffer)
{
        wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_SetAttribute");
        /* TODO */
        return TNC_RESULT_SUCCESS;
}


TNC_Result TNC_TNCS_BindFunction(
        TNC_IMVID imvID,
        char *functionName,
        void **pOutFunctionPointer)
{
        wpa_printf(MSG_DEBUG, "TNC: TNC_TNCS_BindFunction(imcID=%lu, "
                   "functionName='%s')", (unsigned long) imvID, functionName);

        if (tncs_get_imv(imvID) == NULL)
                return TNC_RESULT_INVALID_PARAMETER;

        if (pOutFunctionPointer == NULL)
                return TNC_RESULT_INVALID_PARAMETER;

        if (os_strcmp(functionName, "TNC_TNCS_ReportMessageTypes") == 0)
                *pOutFunctionPointer = TNC_TNCS_ReportMessageTypes;
        else if (os_strcmp(functionName, "TNC_TNCS_SendMessage") == 0)
                *pOutFunctionPointer = TNC_TNCS_SendMessage;
        else if (os_strcmp(functionName, "TNC_TNCS_RequestHandshakeRetry") ==
                 0)
                *pOutFunctionPointer = TNC_TNCS_RequestHandshakeRetry;
        else if (os_strcmp(functionName, "TNC_TNCS_ProvideRecommendation") ==
                 0)
                *pOutFunctionPointer = TNC_TNCS_ProvideRecommendation;
        else if (os_strcmp(functionName, "TNC_TNCS_GetAttribute") == 0)
                *pOutFunctionPointer = TNC_TNCS_GetAttribute;
        else if (os_strcmp(functionName, "TNC_TNCS_SetAttribute") == 0)
                *pOutFunctionPointer = TNC_TNCS_SetAttribute;
        else
                *pOutFunctionPointer = NULL;

        return TNC_RESULT_SUCCESS;
}


static void * tncs_get_sym(void *handle, char *func)
{
        void *fptr;

        fptr = dlsym(handle, func);

        return fptr;
}


static int tncs_imv_resolve_funcs(struct tnc_if_imv *imv)
{
        void *handle = imv->dlhandle;

        /* Mandatory IMV functions */
        imv->Initialize = tncs_get_sym(handle, "TNC_IMV_Initialize");
        if (imv->Initialize == NULL) {
                wpa_printf(MSG_ERROR, "TNC: IMV does not export "
                           "TNC_IMV_Initialize");
                return -1;
        }

        imv->SolicitRecommendation = tncs_get_sym(
                handle, "TNC_IMV_SolicitRecommendation");
        if (imv->SolicitRecommendation == NULL) {
                wpa_printf(MSG_ERROR, "TNC: IMV does not export "
                           "TNC_IMV_SolicitRecommendation");
                return -1;
        }

        imv->ProvideBindFunction =
                tncs_get_sym(handle, "TNC_IMV_ProvideBindFunction");
        if (imv->ProvideBindFunction == NULL) {
                wpa_printf(MSG_ERROR, "TNC: IMV does not export "
                           "TNC_IMV_ProvideBindFunction");
                return -1;
        }

        /* Optional IMV functions */
        imv->NotifyConnectionChange =
                tncs_get_sym(handle, "TNC_IMV_NotifyConnectionChange");
        imv->ReceiveMessage = tncs_get_sym(handle, "TNC_IMV_ReceiveMessage");
        imv->BatchEnding = tncs_get_sym(handle, "TNC_IMV_BatchEnding");
        imv->Terminate = tncs_get_sym(handle, "TNC_IMV_Terminate");

        return 0;
}


static int tncs_imv_initialize(struct tnc_if_imv *imv)
{
        TNC_Result res;
        TNC_Version imv_ver;

        wpa_printf(MSG_DEBUG, "TNC: Calling TNC_IMV_Initialize for IMV '%s'",
                   imv->name);
        res = imv->Initialize(imv->imvID, TNC_IFIMV_VERSION_1,
                              TNC_IFIMV_VERSION_1, &imv_ver);
        wpa_printf(MSG_DEBUG, "TNC: TNC_IMV_Initialize: res=%lu imv_ver=%lu",
                   (unsigned long) res, (unsigned long) imv_ver);

        return res == TNC_RESULT_SUCCESS ? 0 : -1;
}


static int tncs_imv_terminate(struct tnc_if_imv *imv)
{
        TNC_Result res;

        if (imv->Terminate == NULL)
                return 0;

        wpa_printf(MSG_DEBUG, "TNC: Calling TNC_IMV_Terminate for IMV '%s'",
                   imv->name);
        res = imv->Terminate(imv->imvID);
        wpa_printf(MSG_DEBUG, "TNC: TNC_IMV_Terminate: %lu",
                   (unsigned long) res);

        return res == TNC_RESULT_SUCCESS ? 0 : -1;
}


static int tncs_imv_provide_bind_function(struct tnc_if_imv *imv)
{
        TNC_Result res;

        wpa_printf(MSG_DEBUG, "TNC: Calling TNC_IMV_ProvideBindFunction for "
                   "IMV '%s'", imv->name);
        res = imv->ProvideBindFunction(imv->imvID, TNC_TNCS_BindFunction);
        wpa_printf(MSG_DEBUG, "TNC: TNC_IMV_ProvideBindFunction: res=%lu",
                   (unsigned long) res);

        return res == TNC_RESULT_SUCCESS ? 0 : -1;
}


static int tncs_imv_notify_connection_change(struct tnc_if_imv *imv,
                                             TNC_ConnectionID conn,
                                             TNC_ConnectionState state)
{
        TNC_Result res;

        if (imv->NotifyConnectionChange == NULL)
                return 0;

        wpa_printf(MSG_DEBUG, "TNC: Calling TNC_IMV_NotifyConnectionChange(%d)"
                   " for IMV '%s'", (int) state, imv->name);
        res = imv->NotifyConnectionChange(imv->imvID, conn, state);
        wpa_printf(MSG_DEBUG, "TNC: TNC_IMC_NotifyConnectionChange: %lu",
                   (unsigned long) res);

        return res == TNC_RESULT_SUCCESS ? 0 : -1;
}


static int tncs_load_imv(struct tnc_if_imv *imv)
{
        if (imv->path == NULL) {
                wpa_printf(MSG_DEBUG, "TNC: No IMV configured");
                return -1;
        }

        wpa_printf(MSG_DEBUG, "TNC: Opening IMV: %s (%s)",
                   imv->name, imv->path);
        imv->dlhandle = dlopen(imv->path, RTLD_LAZY);
        if (imv->dlhandle == NULL) {
                wpa_printf(MSG_ERROR, "TNC: Failed to open IMV '%s' (%s): %s",
                           imv->name, imv->path, dlerror());
                return -1;
        }

        if (tncs_imv_resolve_funcs(imv) < 0) {
                wpa_printf(MSG_ERROR, "TNC: Failed to resolve IMV functions");
                return -1;
        }

        if (tncs_imv_initialize(imv) < 0 ||
            tncs_imv_provide_bind_function(imv) < 0) {
                wpa_printf(MSG_ERROR, "TNC: Failed to initialize IMV");
                return -1;
        }

        return 0;
}


static void tncs_free_imv(struct tnc_if_imv *imv)
{
        os_free(imv->name);
        os_free(imv->path);
        os_free(imv->supported_types);
}

static void tncs_unload_imv(struct tnc_if_imv *imv)
{
        tncs_imv_terminate(imv);

        if (imv->dlhandle)
                dlclose(imv->dlhandle);

        tncs_free_imv(imv);
}


static int tncs_supported_type(struct tnc_if_imv *imv, unsigned int type)
{
        size_t i;
        unsigned int vendor, subtype;

        if (imv == NULL || imv->supported_types == NULL)
                return 0;

        vendor = type >> 8;
        subtype = type & 0xff;

        for (i = 0; i < imv->num_supported_types; i++) {
                unsigned int svendor, ssubtype;
                svendor = imv->supported_types[i] >> 8;
                ssubtype = imv->supported_types[i] & 0xff;
                if ((vendor == svendor || svendor == TNC_VENDORID_ANY) &&
                    (subtype == ssubtype || ssubtype == TNC_SUBTYPE_ANY))
                        return 1;
        }

        return 0;
}


static void tncs_send_to_imvs(struct tncs_data *tncs, unsigned int type,
                              const u8 *msg, size_t len)
{
        struct tnc_if_imv *imv;
        TNC_Result res;

        wpa_hexdump_ascii(MSG_MSGDUMP, "TNC: Message to IMV(s)", msg, len);

        for (imv = tncs->imv; imv; imv = imv->next) {
                if (imv->ReceiveMessage == NULL ||
                    !tncs_supported_type(imv, type))
                        continue;

                wpa_printf(MSG_DEBUG, "TNC: Call ReceiveMessage for IMV '%s'",
                           imv->name);
                res = imv->ReceiveMessage(imv->imvID, tncs->connectionID,
                                          (TNC_BufferReference) msg, len,
                                          type);
                wpa_printf(MSG_DEBUG, "TNC: ReceiveMessage: %lu",
                           (unsigned long) res);
        }
}


static void tncs_batch_ending(struct tncs_data *tncs)
{
        struct tnc_if_imv *imv;
        TNC_Result res;

        for (imv = tncs->imv; imv; imv = imv->next) {
                if (imv->BatchEnding == NULL)
                        continue;

                wpa_printf(MSG_DEBUG, "TNC: Call BatchEnding for IMV '%s'",
                           imv->name);
                res = imv->BatchEnding(imv->imvID, tncs->connectionID);
                wpa_printf(MSG_DEBUG, "TNC: BatchEnding: %lu",
                           (unsigned long) res);
        }
}


static void tncs_solicit_recommendation(struct tncs_data *tncs)
{
        struct tnc_if_imv *imv;
        TNC_Result res;

        for (imv = tncs->imv; imv; imv = imv->next) {
                if (tncs->imv_data[imv->imvID].recommendation_set)
                        continue;

                wpa_printf(MSG_DEBUG, "TNC: Call SolicitRecommendation for "
                           "IMV '%s'", imv->name);
                res = imv->SolicitRecommendation(imv->imvID,
                                                 tncs->connectionID);
                wpa_printf(MSG_DEBUG, "TNC: SolicitRecommendation: %lu",
                           (unsigned long) res);
        }
}


void tncs_init_connection(struct tncs_data *tncs)
{
        struct tnc_if_imv *imv;
        int i;

        for (imv = tncs->imv; imv; imv = imv->next) {
                tncs_imv_notify_connection_change(
                        imv, tncs->connectionID, TNC_CONNECTION_STATE_CREATE);
                tncs_imv_notify_connection_change(
                        imv, tncs->connectionID,
                        TNC_CONNECTION_STATE_HANDSHAKE);
        }

        for (i = 0; i < TNC_MAX_IMV_ID; i++) {
                os_free(tncs->imv_data[i].imv_send);
                tncs->imv_data[i].imv_send = NULL;
                tncs->imv_data[i].imv_send_len = 0;
        }
}


size_t tncs_total_send_len(struct tncs_data *tncs)
{
        int i;
        size_t len = 0;

        for (i = 0; i < TNC_MAX_IMV_ID; i++)
                len += tncs->imv_data[i].imv_send_len;
        if (tncs->tncs_message)
                len += os_strlen(tncs->tncs_message);
        return len;
}


u8 * tncs_copy_send_buf(struct tncs_data *tncs, u8 *pos)
{
        int i;

        for (i = 0; i < TNC_MAX_IMV_ID; i++) {
                if (tncs->imv_data[i].imv_send == NULL)
                        continue;

                os_memcpy(pos, tncs->imv_data[i].imv_send,
                          tncs->imv_data[i].imv_send_len);
                pos += tncs->imv_data[i].imv_send_len;
                os_free(tncs->imv_data[i].imv_send);
                tncs->imv_data[i].imv_send = NULL;
                tncs->imv_data[i].imv_send_len = 0;
        }

        if (tncs->tncs_message) {
                size_t len = os_strlen(tncs->tncs_message);
                os_memcpy(pos, tncs->tncs_message, len);
                pos += len;
                os_free(tncs->tncs_message);
                tncs->tncs_message = NULL;
        }

        return pos;
}


char * tncs_if_tnccs_start(struct tncs_data *tncs)
{
        char *buf = os_malloc(1000);
        if (buf == NULL)
                return NULL;
        tncs->last_batchid++;
        os_snprintf(buf, 1000, IF_TNCCS_START, tncs->last_batchid);
        return buf;
}


char * tncs_if_tnccs_end(void)
{
        char *buf = os_malloc(100);
        if (buf == NULL)
                return NULL;
        os_snprintf(buf, 100, IF_TNCCS_END);
        return buf;
}


static int tncs_get_type(char *start, unsigned int *type)
{
        char *pos = os_strstr(start, "<Type>");
        if (pos == NULL)
                return -1;
        pos += 6;
        *type = strtoul(pos, NULL, 16);
        return 0;
}


static unsigned char * tncs_get_base64(char *start, size_t *decoded_len)
{
        char *pos, *pos2;
        unsigned char *decoded;

        pos = os_strstr(start, "<Base64>");
        if (pos == NULL)
                return NULL;

        pos += 8;
        pos2 = os_strstr(pos, "</Base64>");
        if (pos2 == NULL)
                return NULL;
        *pos2 = '\0';

        decoded = base64_decode((unsigned char *) pos, os_strlen(pos),
                                decoded_len);
        *pos2 = '<';
        if (decoded == NULL) {
                wpa_printf(MSG_DEBUG, "TNC: Failed to decode Base64 data");
        }

        return decoded;
}


static enum tncs_process_res tncs_derive_recommendation(struct tncs_data *tncs)
{
        enum IMV_Action_Recommendation rec;
        struct tnc_if_imv *imv;
        TNC_ConnectionState state;
        char *txt;

        wpa_printf(MSG_DEBUG, "TNC: No more messages from IMVs");

        if (tncs->done)
                return TNCCS_PROCESS_OK_NO_RECOMMENDATION;

        tncs_solicit_recommendation(tncs);

        /* Select the most restrictive recommendation */
        rec = TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION;
        for (imv = tncs->imv; imv; imv = imv->next) {
                TNC_IMV_Action_Recommendation irec;
                irec = tncs->imv_data[imv->imvID].recommendation;
                if (irec == TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS)
                        rec = TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS;
                if (irec == TNC_IMV_ACTION_RECOMMENDATION_ISOLATE &&
                    rec != TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS)
                        rec = TNC_IMV_ACTION_RECOMMENDATION_ISOLATE;
                if (irec == TNC_IMV_ACTION_RECOMMENDATION_ALLOW &&
                    rec == TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION)
                        rec = TNC_IMV_ACTION_RECOMMENDATION_ALLOW;
        }

        wpa_printf(MSG_DEBUG, "TNC: Recommendation: %d", rec);
        tncs->recommendation = rec;
        tncs->done = 1;

        txt = NULL;
        switch (rec) {
        case TNC_IMV_ACTION_RECOMMENDATION_ALLOW:
        case TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION:
                txt = "allow";
                state = TNC_CONNECTION_STATE_ACCESS_ALLOWED;
                break;
        case TNC_IMV_ACTION_RECOMMENDATION_ISOLATE:
                txt = "isolate";
                state = TNC_CONNECTION_STATE_ACCESS_ISOLATED;
                break;
        case TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS:
                txt = "none";
                state = TNC_CONNECTION_STATE_ACCESS_NONE;
                break;
        default:
                state = TNC_CONNECTION_STATE_ACCESS_ALLOWED;
                break;
        }

        if (txt) {
                os_free(tncs->tncs_message);
                tncs->tncs_message = os_zalloc(200);
                if (tncs->tncs_message) {
                        os_snprintf(tncs->tncs_message, 199,
                                    "<TNCC-TNCS-Message><Type>%08X</Type>"
                                    "<XML><TNCCS-Recommendation type=\"%s\">"
                                    "</TNCCS-Recommendation></XML>"
                                    "</TNCC-TNCS-Message>",
                                    TNC_TNCCS_RECOMMENDATION, txt);
                }
        }

        for (imv = tncs->imv; imv; imv = imv->next) {
                tncs_imv_notify_connection_change(imv, tncs->connectionID,
                                                  state);
        }

        switch (rec) {
        case TNC_IMV_ACTION_RECOMMENDATION_ALLOW:
                return TNCCS_RECOMMENDATION_ALLOW;
        case TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS:
                return TNCCS_RECOMMENDATION_NO_ACCESS;
        case TNC_IMV_ACTION_RECOMMENDATION_ISOLATE:
                return TNCCS_RECOMMENDATION_ISOLATE;
        case TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION:
                return TNCCS_RECOMMENDATION_NO_RECOMMENDATION;
        default:
                return TNCCS_PROCESS_ERROR;
        }
}


enum tncs_process_res tncs_process_if_tnccs(struct tncs_data *tncs,
                                            const u8 *msg, size_t len)
{
        char *buf, *start, *end, *pos, *pos2, *payload;
        unsigned int batch_id;
        unsigned char *decoded;
        size_t decoded_len;

        buf = os_malloc(len + 1);
        if (buf == NULL)
                return TNCCS_PROCESS_ERROR;

        os_memcpy(buf, msg, len);
        buf[len] = '\0';
        start = os_strstr(buf, "<TNCCS-Batch ");
        end = os_strstr(buf, "</TNCCS-Batch>");
        if (start == NULL || end == NULL || start > end) {
                os_free(buf);
                return TNCCS_PROCESS_ERROR;
        }

        start += 13;
        while (*start == ' ')
                start++;
        *end = '\0';

        pos = os_strstr(start, "BatchId=");
        if (pos == NULL) {
                os_free(buf);
                return TNCCS_PROCESS_ERROR;
        }

        pos += 8;
        if (*pos == '"')
                pos++;
        batch_id = atoi(pos);
        wpa_printf(MSG_DEBUG, "TNC: Received IF-TNCCS BatchId=%u",
                   batch_id);
        if (batch_id != tncs->last_batchid + 1) {
                wpa_printf(MSG_DEBUG, "TNC: Unexpected IF-TNCCS BatchId "
                           "%u (expected %u)",
                           batch_id, tncs->last_batchid + 1);
                os_free(buf);
                return TNCCS_PROCESS_ERROR;
        }
        tncs->last_batchid = batch_id;

        while (*pos != '\0' && *pos != '>')
                pos++;
        if (*pos == '\0') {
                os_free(buf);
                return TNCCS_PROCESS_ERROR;
        }
        pos++;
        payload = start;

        /*
         * <IMC-IMV-Message>
         * <Type>01234567<  ype>
         * <Base64>foo==</Base64>
         * </IMC-IMV-Message>
         */

        while (*start) {
                char *endpos;
                unsigned int type;

                pos = os_strstr(start, "<IMC-IMV-Message>");
                if (pos == NULL)
                        break;
                start = pos + 17;
                end = os_strstr(start, "</IMC-IMV-Message>");
                if (end == NULL)
                        break;
                *end = '\0';
                endpos = end;
                end += 18;

                if (tncs_get_type(start, &type) < 0) {
                        *endpos = '<';
                        start = end;
                        continue;
                }
                wpa_printf(MSG_DEBUG, "TNC: IMC-IMV-Message Type 0x%x", type);

                decoded = tncs_get_base64(start, &decoded_len);
                if (decoded == NULL) {
                        *endpos = '<';
                        start = end;
                        continue;
                }

                tncs_send_to_imvs(tncs, type, decoded, decoded_len);

                os_free(decoded);

                start = end;
        }

        /*
         * <TNCC-TNCS-Message>
         * <Type>01234567<  ype>
         * <XML><TNCCS-Foo type="foo"><  NCCS-Foo></XML>
         * <Base64>foo==</Base64>
         * <  NCC-TNCS-Message>
         */

        start = payload;
        while (*start) {
                unsigned int type;
                char *xml, *xmlend, *endpos;

                pos = os_strstr(start, "<TNCC-TNCS-Message>");
                if (pos == NULL)
                        break;
                start = pos + 19;
                end = os_strstr(start, "</TNCC-TNCS-Message>");
                if (end == NULL)
                        break;
                *end = '\0';
                endpos = end;
                end += 20;

                if (tncs_get_type(start, &type) < 0) {
                        *endpos = '<';
                        start = end;
                        continue;
                }
                wpa_printf(MSG_DEBUG, "TNC: TNCC-TNCS-Message Type 0x%x",
                           type);

                /* Base64 OR XML */
                decoded = NULL;
                xml = NULL;
                xmlend = NULL;
                pos = os_strstr(start, "<XML>");
                if (pos) {
                        pos += 5;
                        pos2 = os_strstr(pos, "</XML>");
                        if (pos2 == NULL) {
                                *endpos = '<';
                                start = end;
                                continue;
                        }
                        xmlend = pos2;
                        xml = pos;
                } else {
                        decoded = tncs_get_base64(start, &decoded_len);
                        if (decoded == NULL) {
                                *endpos = '<';
                                start = end;
                                continue;
                        }
                }

                if (decoded) {
                        wpa_hexdump_ascii(MSG_MSGDUMP,
                                          "TNC: TNCC-TNCS-Message Base64",
                                          decoded, decoded_len);
                        os_free(decoded);
                }

                if (xml) {
                        wpa_hexdump_ascii(MSG_MSGDUMP,
                                          "TNC: TNCC-TNCS-Message XML",
                                          (unsigned char *) xml,
                                          xmlend - xml);
                }

                start = end;
        }

        os_free(buf);

        tncs_batch_ending(tncs);

        if (tncs_total_send_len(tncs) == 0)
                return tncs_derive_recommendation(tncs);

        return TNCCS_PROCESS_OK_NO_RECOMMENDATION;
}


static struct tnc_if_imv * tncs_parse_imv(int id, char *start, char *end,
                                          int *error)
{
        struct tnc_if_imv *imv;
        char *pos, *pos2;

        if (id >= TNC_MAX_IMV_ID) {
                wpa_printf(MSG_DEBUG, "TNC: Too many IMVs");
                return NULL;
        }

        imv = os_zalloc(sizeof(*imv));
        if (imv == NULL) {
                *error = 1;
                return NULL;
        }

        imv->imvID = id;

        pos = start;
        wpa_printf(MSG_DEBUG, "TNC: Configured IMV: %s", pos);
        if (pos + 1 >= end || *pos != '"') {
                wpa_printf(MSG_ERROR, "TNC: Ignoring invalid IMV line '%s' "
                           "(no starting quotation mark)", start);
                os_free(imv);
                return NULL;
        }

        pos++;
        pos2 = pos;
        while (pos2 < end && *pos2 != '"')
                pos2++;
        if (pos2 >= end) {
                wpa_printf(MSG_ERROR, "TNC: Ignoring invalid IMV line '%s' "
                           "(no ending quotation mark)", start);
                os_free(imv);
                return NULL;
        }
        *pos2 = '\0';
        wpa_printf(MSG_DEBUG, "TNC: Name: '%s'", pos);
        imv->name = os_strdup(pos);

        pos = pos2 + 1;
        if (pos >= end || *pos != ' ') {
                wpa_printf(MSG_ERROR, "TNC: Ignoring invalid IMV line '%s' "
                           "(no space after name)", start);
                os_free(imv);
                return NULL;
        }

        pos++;
        wpa_printf(MSG_DEBUG, "TNC: IMV file: '%s'", pos);
        imv->path = os_strdup(pos);

        return imv;
}


static int tncs_read_config(struct tncs_global *global)
{
        char *config, *end, *pos, *line_end;
        size_t config_len;
        struct tnc_if_imv *imv, *last;
        int id = 0;

        last = NULL;

        config = os_readfile(TNC_CONFIG_FILE, &config_len);
        if (config == NULL) {
                wpa_printf(MSG_ERROR, "TNC: Could not open TNC configuration "
                           "file '%s'", TNC_CONFIG_FILE);
                return -1;
        }

        end = config + config_len;
        for (pos = config; pos < end; pos = line_end + 1) {
                line_end = pos;
                while (*line_end != '\n' && *line_end != '\r' &&
                       line_end < end)
                        line_end++;
                *line_end = '\0';

                if (os_strncmp(pos, "IMV ", 4) == 0) {
                        int error = 0;

                        imv = tncs_parse_imv(id++, pos + 4, line_end, &error);
                        if (error)
                                return -1;
                        if (imv) {
                                if (last == NULL)
                                        global->imv = imv;
                                else
                                        last->next = imv;
                                last = imv;
                        }
                }
        }

        os_free(config);

        return 0;
}


struct tncs_data * tncs_init(void)
{
        struct tncs_data *tncs;

        if (tncs_global_data == NULL)
                return NULL;

        tncs = os_zalloc(sizeof(*tncs));
        if (tncs == NULL)
                return NULL;
        tncs->imv = tncs_global_data->imv;
        tncs->connectionID = tncs_global_data->next_conn_id++;
        tncs->next = tncs_global_data->connections;
        tncs_global_data->connections = tncs;

        return tncs;
}


void tncs_deinit(struct tncs_data *tncs)
{
        int i;
        struct tncs_data *prev, *conn;

        if (tncs == NULL)
                return;

        for (i = 0; i < TNC_MAX_IMV_ID; i++)
                os_free(tncs->imv_data[i].imv_send);

        prev = NULL;
        conn = tncs_global_data->connections;
        while (conn) {
                if (conn == tncs) {
                        if (prev)
                                prev->next = tncs->next;
                        else
                                tncs_global_data->connections = tncs->next;
                        break;
                }
                prev = conn;
                conn = conn->next;
        }

        os_free(tncs->tncs_message);
        os_free(tncs);
}


int tncs_global_init(void)
{
        struct tnc_if_imv *imv;

        tncs_global_data = os_zalloc(sizeof(*tncs_global_data));
        if (tncs_global_data == NULL)
                return -1;

        if (tncs_read_config(tncs_global_data) < 0) {
                wpa_printf(MSG_ERROR, "TNC: Failed to read TNC configuration");
                goto failed;
        }

        for (imv = tncs_global_data->imv; imv; imv = imv->next) {
                if (tncs_load_imv(imv)) {
                        wpa_printf(MSG_ERROR, "TNC: Failed to load IMV '%s'",
                                   imv->name);
                        goto failed;
                }
        }

        return 0;

failed:
        tncs_global_deinit();
        return -1;
}


void tncs_global_deinit(void)
{
        struct tnc_if_imv *imv, *prev;

        if (tncs_global_data == NULL)
                return;

        imv = tncs_global_data->imv;
        while (imv) {
                tncs_unload_imv(imv);

                prev = imv;
                imv = imv->next;
                os_free(prev);
        }

        os_free(tncs_global_data);
}


struct wpabuf * tncs_build_soh_request(void)
{
        struct wpabuf *buf;

        /*
         * Build a SoH Request TLV (to be used inside SoH EAP Extensions
         * Method)
         */

        buf = wpabuf_alloc(8 + 4);
        if (buf == NULL)
                return NULL;

        /* Vendor-Specific TLV (Microsoft) - SoH Request */
        wpabuf_put_be16(buf, EAP_TLV_VENDOR_SPECIFIC_TLV); /* TLV Type */
        wpabuf_put_be16(buf, 8); /* Length */

        wpabuf_put_be32(buf, EAP_VENDOR_MICROSOFT); /* Vendor_Id */

        wpabuf_put_be16(buf, 0x02); /* TLV Type - SoH Request TLV */
        wpabuf_put_be16(buf, 0); /* Length */

        return buf;
}


struct wpabuf * tncs_process_soh(const u8 *soh_tlv, size_t soh_tlv_len,
                                 int *failure)
{
        wpa_hexdump(MSG_DEBUG, "TNC: SoH TLV", soh_tlv, soh_tlv_len);
        *failure = 0;

        /* TODO: return MS-SoH Response TLV */

        return NULL;
}
